package com.pb.wkflow.api.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.PageInfo;
import com.github.pagehelper.page.PageMethod;
import com.pb.infra.oth.entity.common.Result;
import com.pb.infra.oth.service.BaseService;
import com.pb.infra.oth.util.UserInfo;
import com.pb.wkflow.api.config.Constants;
import com.pb.wkflow.api.controller.assembler.ProcDefineAssembler;
import com.pb.wkflow.api.controller.assembler.WfSysJsonXmlAssembler;
import com.pb.wkflow.api.controller.request.ProcDefineCreateRequest;
import com.pb.wkflow.api.controller.request.ProcDefineJsonRequest;
import com.pb.wkflow.api.controller.request.ProcDefineQueryRequest;
import com.pb.wkflow.api.controller.request.ProcessInstanceRequest;
import com.pb.wkflow.api.entity.ProcessDefineEntity;
import com.pb.wkflow.api.entity.WfSysClassifyEntity;
import com.pb.wkflow.api.entity.WfSysJsonXmlEntity;
import com.pb.wkflow.api.exception.ApprovalException;
import com.pb.wkflow.api.mapper.WfClassifyMapper;
import com.pb.wkflow.api.mapper.WfSysJsonXmlMapper;
import com.pb.wkflow.api.mapper.WfSysProcDefFieldMapper;
import com.pb.wkflow.api.mapper.WfSysProcInfoMapper;
import com.pb.wkflow.api.service.ProcDefineService;
import com.pb.wkflow.api.service.WfSysProcDefFieldService;
import com.pb.wkflow.core.handler.QueryHandler;
import com.pb.wkflow.core.handler.QueryResultHandler;
import com.pb.wkflow.core.service.WorkflowDefinitionService;
import com.pb.wkflow.core.service.WorkflowInstanceService;
import com.pb.wkflow.core.service.WorkflowModelService;
import com.pb.wkflow.core.utils.AssertUtils;
import com.pb.wkflow.core.utils.DateUtils;
import lombok.extern.slf4j.Slf4j;
import org.camunda.bpm.engine.ProcessEngine;
import org.camunda.bpm.engine.RepositoryService;
import org.camunda.bpm.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.camunda.bpm.engine.repository.Deployment;
import org.camunda.bpm.engine.repository.ProcessDefinition;
import org.camunda.bpm.engine.repository.ProcessDefinitionQuery;
import org.camunda.bpm.model.bpmn.Bpmn;
import org.camunda.bpm.model.bpmn.BpmnModelInstance;
import org.camunda.bpm.model.bpmn.instance.Definitions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 流程定义服务
 * @author 苗益辉
 * @date 2022/9/1 15:18
 */
@Slf4j
@Service
public class ProcDefineServiceImpl extends BaseService implements ProcDefineService {

    /**
     * repositoryService
     */
    @Autowired
    RepositoryService repositoryService;

    /**
     * processEngine
     */
    @Autowired
    ProcessEngine processEngine;

    /**
     * wfClassifyMapper
     */
    @Autowired
    WfClassifyMapper wfClassifyMapper;

    /**
     * wfSysJsonXmlMapper
     */
    @Autowired
    WfSysJsonXmlMapper wfSysJsonXmlMapper;

    /**
     * wfSysProcInfoMapper
     */
    @Autowired
    WfSysProcInfoMapper wfSysProcInfoMapper;

    /**
     * wfSysProcDefFieldMapper
     */
    @Autowired
    WfSysProcDefFieldMapper wfSysProcDefFieldMapper;

    /**
     * queryHandler
     */
    @Autowired
    QueryHandler queryHandler;

    /**
     * 流程模型服务
     */
    @Autowired
    WorkflowModelService workflowModelService;

    /**
     * 流程定义服务
     */
    @Autowired
    WorkflowDefinitionService workflowDefinitionService;

    /**
     * 流程实例服务
     */
    @Autowired
    WorkflowInstanceService workflowInstanceService;

    /**
     * 流程信息
     */
    @Autowired
    WfSysProcDefFieldService wfSysProcDefFieldService;

    /**
     * 流程键值校验
     */
    private static final String PROC_DEF_REGEX = "^[_a-zA-Z].*";

    /**
     * 创建空的bpmn，用于第一次创建
     * @param request 请求
     * @return Result
     */
    @Override
    public Result createEmptyBpmnModel(ProcDefineCreateRequest request) {
        String cropId = this.getCurrentUserInfo().getCorpId();
        request.setTenantId(cropId);
        AssertUtils.notEmpty(request.getKey(), "流程键值不能为空");
        AssertUtils.notTrue(request.getKey().matches(PROC_DEF_REGEX), "流程键值必须以字母或者下划线开头");
        AssertUtils.notEmpty(request.getName(),"流程名称不能为空");
        //判断流程键值是否已存在
        List<ProcessDefinition> list = workflowDefinitionService.findListByProcessDefineKey(
                request.getKey(),
                request.getTenantId()
        );
        if (!list.isEmpty()) {
            throw new ApprovalException("该流程键值已存在");
        }
        //创建空画布作为初次创建
        BpmnModelInstance emptyModel = Bpmn
                .createExecutableProcess(request.getKey())
                .name(request.getName())
                .documentation(request.getDescription())
                .done();
        workflowModelService.createEmptyBpmnModel(emptyModel);
        wfSysProcDefFieldService.add(request);
        Definitions definitions = emptyModel.getDefinitions();
        definitions.setTargetNamespace(request.getClassifyId());
        Deployment deployment = workflowDefinitionService.deployByModel(
                request.getKey(),
                emptyModel,
                request.getTenantId()
        );
        Map<String, Object> resultMap = new HashMap<>(2);
        resultMap.put(Constants.DEPLOYMENT_ID, deployment.getId());
        return Result.ok(resultMap).setMessage("部署成功");
    }

    /**
     * 保存json
     *
     * 1、新建一张表，建立procDefineKey + version + json 的关联信息
     * 另外可以添加版本描述，作为后续区分的重要信息，编辑流程时，弹出分页查看所有版本，以及对应的json，点击明细进入流程设计器
     * 2、根据procDefineId 查询流程定义
     * 3、流程定义键值 + （已发布的版本号 + 1） 或 未发布的版本号 + request.getJsonXml() 入库
     * 如果表里没有未发布版本号，则version为 最新版本号 + 1
     * 如果标有有未发布版本号，则 替换原有数据
     * 如果表里没有数据，则默认 version 为 1
     * 保证只有一个未发布版本号，以及已发布版本与流程定义版本一致
     * @param request 请求
     * @return Result
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result saveJsonModel(ProcDefineCreateRequest request) {
        UserInfo userInfo = this.getCurrentUserInfo();
        request.setTenantId(userInfo.getCorpId());
        AssertUtils.notEmpty(request.getJsonXml(), "流程图不能为空");
        AssertUtils.notEmpty(request.getKey(), "流程键值不能为空");
        //判断流程键值是否已存在
        List<ProcessDefinition> list = workflowDefinitionService.findListByProcessDefineKey(
                request.getKey(),
                request.getTenantId()
        );
        if (list.isEmpty()) {
            throw new ApprovalException("该流程键值还未创建");
        }
        // 检查 jsonXml 是否符合标准
        JSONArray childShapes = JSON.parseObject(request.getJsonXml()).getJSONArray(Constants.CHILD_SHAPES);
        //判断是否存在排他网关
        for (Object childShape : childShapes) {
            JSONObject shape = (JSONObject) childShape;
            String type = shape.getJSONObject(Constants.GENERAL).getString("type");
            String id = shape.getJSONObject(Constants.GENERAL).getString("id");
            int defaultCount = 0;
            if ("exclusiveGateway".equals(type)) {
                boolean hasDefault = false;
                for (Object childShape1 : childShapes) {
                    JSONObject shape1 = (JSONObject) childShape1;
                    String flowType = shape1.getJSONObject(Constants.GENERAL).getString("type");
                    //连接线
                    if ("sequenceFlow".equals(flowType)) {
                        String sourceId = shape1.getJSONObject(Constants.GENERAL).getString("sourceId");
                        if (id.equals(sourceId)) {
                            //属于排他网关的连接线
                            String defaultStatus = shape1.getJSONObject("properties").getString("default");
                            if ("true".equals(defaultStatus)) {
                                hasDefault = true;
                                defaultCount ++;
                            }
                        }
                    }
                }
                //当前结果
                if (!hasDefault) {
                    throw new ApprovalException("排他网关必须要有默认流转线");
                }
                if (defaultCount > 1) {
                    throw new ApprovalException("排他网关不能有多个默认流转线");
                }
            }
        }
        WfSysJsonXmlEntity wfSysJsonXmlEntity = WfSysJsonXmlAssembler.assemblerToQuery(request,userInfo);
        WfSysJsonXmlEntity entity = wfSysJsonXmlMapper.selectByVersion(wfSysJsonXmlEntity);
        if (entity == null) {
            // 如果未新建过,默认版本为1
            WfSysJsonXmlEntity wfSysJsonXmlSaveEntity = WfSysJsonXmlAssembler.assemblerToSave(request,userInfo,1);
            wfSysJsonXmlMapper.insert(wfSysJsonXmlSaveEntity);
        } else if (entity.getIsDeploy() == 1) {
            // 如果新建过，且无未发布的流程图
            WfSysJsonXmlEntity wfSysJsonXmlSaveEntity =
                    WfSysJsonXmlAssembler.assemblerToSave(request,userInfo,entity.getVer() + 1);
            wfSysJsonXmlMapper.insert(wfSysJsonXmlSaveEntity);
        } else if (entity.getIsDeploy() == 0) {
            WfSysJsonXmlAssembler.assemblerToUpdate(request, entity);
            wfSysJsonXmlMapper.updateJson(entity);
        }
        return Result.ok("保存成功");
    }

    /**
     * 部署流程，非流程图保存及发布
     * 如果未发布过，则 camunda 只有一个空画布的流程发布，解析jsonXml然后替换第一版本的流程
     * 如果发布过，则 正常发布后续版本
     * @param request 请求
     * @return Result
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result deployModel(ProcDefineJsonRequest request) {
        String cropId = this.getCurrentUserInfo().getCorpId();
        request.setTenantId(cropId);
        Map<String, Object> resultMap = new HashMap<>(2);
        AssertUtils.notEmpty(request.getId(), "流程图主键不能为空");
        WfSysJsonXmlEntity wfSysJsonXmlEntity = wfSysJsonXmlMapper.selectById(request.getId());
        AssertUtils.notNull(wfSysJsonXmlEntity, "流程图不存在");
        ProcessDefinition processDefinition = this.findLastVersionProcDefine(
                wfSysJsonXmlEntity.getProcDefKey(),
                request.getTenantId()
        );
//        String deployId = processDefinition.getDeploymentId();
//        if (wfSysJsonXmlEntity.getVer() == 1 && StringUtils.isBlank(wfSysJsonXmlEntity.getProcDefId())) {
//            // 未发布过,解析jsonXml，然后替换初次部署的空画布
//            BpmnModelInstance modelInstance = Bpmn.createExecutableProcess(wfSysJsonXmlEntity.getProcDefKey())
//                    .name(processDefinition.getName()).documentation(wfSysJsonXmlEntity.getDescription()).done();
//            JSONArray childShapes = JSON.parseObject(wfSysJsonXmlEntity.getJsonXml())
//                    .getJSONArray(Constants.CHILD_SHAPES);
//            workflowModelService.createBpmnModel(modelInstance, childShapes, processDefinition);
//            DeploymentEntity deployment = workflowDefinitionService
//                    .findByDeploymentId(processDefinition.getDeploymentId());
//            final ByteArrayEntity byteArrayEntity =
//                    new ByteArrayEntity(wfSysJsonXmlEntity.getProcDefKey() + ".bpmn",
//                            Bpmn.convertToString(modelInstance).getBytes(StandardCharsets.UTF_8));
//            byteArrayEntity.setDeploymentId(deployment.getId());
//            byteArrayEntity.setTenantId(wfSysJsonXmlEntity.getTenantId());
//            ProcessEngineConfigurationImpl processEngineConfiguration = (ProcessEngineConfigurationImpl) processEngine
//                    .getProcessEngineConfiguration();
//            CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutorTxRequired();
//            commandExecutor.execute(commandContext -> {
//                List<ResourceEntity> rList = commandContext.getResourceManager().findResourcesByDeploymentId(deployId);
//                for (ResourceEntity resEntity : rList) {
//                    //删除原来的部署信息，即初次部署的空画布
//                    commandContext.getByteArrayManager().delete(resEntity);
//                }
//                commandContext.getByteArrayManager().insertByteArray(byteArrayEntity);
//                return null;
//            });
//            //查询最新流程id
//            processDefinition = this.findLastVersionProcDefine(
//                    wfSysJsonXmlEntity.getProcDefKey(),
//                    wfSysJsonXmlEntity.getTenantId()
//            );
//            WfSysJsonXmlAssembler.assemblerToDeploy(wfSysJsonXmlEntity, processDefinition);
//            wfSysJsonXmlMapper.deploy(wfSysJsonXmlEntity);
//            resultMap.put(Constants.DEPLOYMENT_ID, deployment.getId());
//            resultMap.put("processDefineId", processDefinition.getId());
//            resultMap.put("version", processDefinition.getVersion());
//        } else {
        // 发布过，更新流程
        BpmnModelInstance modelInstance = Bpmn.createExecutableProcess(wfSysJsonXmlEntity.getProcDefKey())
                .name(processDefinition.getName()).documentation(wfSysJsonXmlEntity.getDescription()).done();
        JSONArray childShapes = JSON.parseObject(wfSysJsonXmlEntity.getJsonXml())
                .getJSONArray(Constants.CHILD_SHAPES);
        workflowModelService.createBpmnModel(modelInstance, childShapes, processDefinition);
        Deployment deployment = workflowDefinitionService.deployByModel(
                wfSysJsonXmlEntity.getProcDefKey(),
                modelInstance,
                request.getTenantId()
        );
        //查询最新流程id
        processDefinition = this.findLastVersionProcDefine(
                wfSysJsonXmlEntity.getProcDefKey(),
                request.getTenantId()
        );
        WfSysJsonXmlAssembler.assemblerToDeploy(wfSysJsonXmlEntity, processDefinition);
        wfSysJsonXmlMapper.deploy(wfSysJsonXmlEntity);
        List<WfSysJsonXmlEntity> useWfSysJsonXmls = wfSysJsonXmlMapper.selectUsedByProcDefKey(wfSysJsonXmlEntity.getProcDefKey(), request.getTenantId());
        if (useWfSysJsonXmls == null || useWfSysJsonXmls.isEmpty()) {
            wfSysJsonXmlMapper.updateUseByProcDefId(wfSysJsonXmlEntity.getProcDefId());
        }
        resultMap.put(Constants.DEPLOYMENT_ID, deployment.getId());
        resultMap.put("processDefineId", processDefinition.getId());
        resultMap.put("version", processDefinition.getVersion());
//        }
        return Result.ok(resultMap).setMessage("部署成功");
    }

    /**
     * 保存并部署流程
     * @param request 请求
     * @return Result
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result saveAndDeployModel(ProcDefineCreateRequest request) {
        //保存流程图
        this.saveJsonModel(request);
        UserInfo userInfo = this.getCurrentUserInfo();
        WfSysJsonXmlEntity wfSysJsonXmlEntity = wfSysJsonXmlMapper
                .selectUnDeploy(WfSysJsonXmlAssembler.assemblerToQuery(request, userInfo));
        if (wfSysJsonXmlEntity != null) {
            ProcDefineJsonRequest defineJsonRequest = new ProcDefineJsonRequest();
            defineJsonRequest.setId(wfSysJsonXmlEntity.getId());
            return this.deployModel(defineJsonRequest);
        } else {
            return Result.error("500", "找不到未部署的流程");
        }
    }

    /**
     * 查询json列表
     * @param request request
     * @param pageSize 页数
     * @param pageNum 页码
     * @return Result
     */
    @Override
    public Result getJsonList(ProcDefineJsonRequest request, Integer pageSize, Integer pageNum) {
        UserInfo userInfo = this.getCurrentUserInfo();
        if (pageNum != null && pageSize != null) {
            PageMethod.startPage(pageNum, pageSize);
        }
        WfSysJsonXmlEntity wfSysJsonXmlEntity = WfSysJsonXmlAssembler.assemblerToQuery(request ,userInfo);
        List<WfSysJsonXmlEntity> list = wfSysJsonXmlMapper.selectList(wfSysJsonXmlEntity);
        return Result.ok(new PageInfo<WfSysJsonXmlEntity>(list).getTotal(), list);
    }

    /**
     * 获取流程图json
     * @param request request
     * @return Result
     */
    @Override
    public Result getJson(ProcDefineJsonRequest request) {
        UserInfo userInfo = this.getCurrentUserInfo();
        WfSysJsonXmlEntity wfSysJsonXmlEntity = WfSysJsonXmlAssembler.assemblerToQuery(request, userInfo);
        WfSysJsonXmlEntity entity = wfSysJsonXmlMapper.selectByVersion(wfSysJsonXmlEntity);
        if (entity == null) {
            Map<String, Object> result = new HashMap<>(1);
            ProcessDefinition processDefinition = this.findLastVersionProcDefine(
                    request.getProcDefineKey(),
                    userInfo.getCorpId()
            );
            result.put("description", processDefinition.getDescription());
            return Result.ok(result);
        }
        return Result.ok(entity);
    }

    /**
     * 获取部署列表
     * @param request request
     * @param pageSize 页数
     * @param pageNum 页码
     */
    @Override
    public Result getDeployList(ProcDefineQueryRequest request, Integer pageSize, Integer pageNum) {
        String cropId = this.getCurrentUserInfo().getCorpId();
        request.setTenantIds(cropId);
        //查询流程分类
        List<WfSysClassifyEntity> classifyEntities = wfClassifyMapper.getAbleList(request.getTenantIds(), null);
        Map<String, String> classifyMap = new HashMap<>(2);
        for (WfSysClassifyEntity classifyEntity : classifyEntities) {
            classifyMap.put(classifyEntity.getId(), classifyEntity.getClassifyName());
        }
        //查询流程列表
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
        ProcDefineAssembler.assemblerToQuery(processDefinitionQuery, request);
        Result result = queryHandler.commonPageQuery(processDefinitionQuery, pageNum, pageSize,
                (QueryResultHandler<ProcessDefineEntity, ProcessDefinitionEntity>) processDefinition -> {
                    return ProcessDefineEntity.builder()
                            .deployKey(processDefinition.getKey())
                            .deployName(processDefinition.getName())
                            .classifyCode(processDefinition.getCategory())
                            .classifyName(classifyMap.get(processDefinition.getCategory()))
                            .tenantId(processDefinition.getTenantId())
                            .build();
                });
        List<ProcessDefineEntity> processDefineEntities = (List<ProcessDefineEntity>) result.getData();
        List<String> procDefKeys = processDefineEntities.stream().map(ProcessDefineEntity::getDeployKey).collect(Collectors.toList());
        Map<String, WfSysJsonXmlEntity> allUsedMap = new HashMap<>();
        Map<String, WfSysJsonXmlEntity> maxVersionMap = new HashMap<>();
        if (!procDefKeys.isEmpty()) {
            List<WfSysJsonXmlEntity> allUsedList = wfSysJsonXmlMapper.selectAllUsed(cropId, procDefKeys);
            allUsedMap = allUsedList.stream().collect(
                    Collectors.toMap(
                            WfSysJsonXmlEntity::getProcDefKey,
                            a -> a, (k1, k2) -> k1));
            List<WfSysJsonXmlEntity> maxVersionList = wfSysJsonXmlMapper.selectAllMaxVersion(cropId, procDefKeys);
            maxVersionMap = maxVersionList.stream().collect(
                    Collectors.toMap(
                            WfSysJsonXmlEntity::getProcDefKey,
                            a -> a, (k1, k2) -> k1));

        }
        for (int i = 0; i < processDefineEntities.size(); i++) {
            ProcessDefineEntity processDefinition = processDefineEntities.get(i);
            WfSysJsonXmlEntity wfSysJsonXmlEntity = null;
            if (allUsedMap.containsKey(processDefinition.getDeployKey())) {
                wfSysJsonXmlEntity = allUsedMap.get(processDefinition.getDeployKey());
            } else if (maxVersionMap.containsKey(processDefinition.getDeployKey())) {
                wfSysJsonXmlEntity = maxVersionMap.get(processDefinition.getDeployKey());
            }
            processDefinition.setId(wfSysJsonXmlEntity == null ? null : wfSysJsonXmlEntity.getProcDefId());
            processDefinition.setDeployVersion(wfSysJsonXmlEntity == null ? null : wfSysJsonXmlEntity.getVer());
            processDefinition.setDescription(wfSysJsonXmlEntity == null ? null : wfSysJsonXmlEntity.getDescription());
        }
        return result.setData(processDefineEntities);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteProcess(ProcessInstanceRequest request) {
        String cropId = this.getCurrentUserInfo().getCorpId();
        AssertUtils.notEmpty(request.getProcessInstanceKey(), "流程定义不能为空");
        AssertUtils.notTrue(
                workflowInstanceService.isActiveProcInstance(request.getProcessInstanceKey()) == 0,
                "存在进行中的流程，不能删除");
        List<ProcessDefinition> processDefinitions = workflowDefinitionService
                .findListByProcessDefineKey(request.getProcessInstanceKey(), cropId);
        for (int i = 0; i < processDefinitions.size(); i++) {
            String procDefineId = processDefinitions.get(i).getId();
            workflowDefinitionService.deleteProcessDefine(procDefineId);
            wfSysProcInfoMapper.deleteByProcessDefineId(procDefineId);
        }
        wfSysJsonXmlMapper.deleteJson(request.getProcessInstanceKey(), cropId);
        wfSysProcDefFieldMapper.delete(request.getProcessInstanceKey(), cropId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void use(ProcessInstanceRequest request) {
        String cropId = this.getCurrentUserInfo().getCorpId();
        AssertUtils.notEmpty(request.getProcessInstanceKey(), "流程定义键值不能为空");
        AssertUtils.notEmpty(request.getProcessInstanceId(), "使用的流程定义id不能为空");
        wfSysJsonXmlMapper.updateUnUseByProcDefKey(request.getProcessInstanceKey(), cropId);
        wfSysJsonXmlMapper.updateUseByProcDefId(request.getProcessInstanceId());
    }

    @Override
    public void editDescription(ProcDefineJsonRequest request) {
        AssertUtils.notEmpty(request.getProcDefineId(), "流程暂未发布，请发布后重试");
        AssertUtils.notEmpty(request.getDescription(), "版本描述不能为空");
        wfSysJsonXmlMapper.updateDescription(request.getProcDefineId(), request.getDescription());
    }

    @Override
    public void downModel(String defineId, HttpServletResponse response) {
        //根据流程定义id 查询 流程信息
        //拼装流程实例信息
        //查询json信息
        //保存为zip导出
        //TODO
    }

    /**
     * 根据流程键值和租户id查询最新版本流程
     * @param procDefineKey 流程键值
     * @param tenantId 租户id
     * @return ProcessDefinition
     */
    public ProcessDefinition findLastVersionProcDefine(String procDefineKey, String tenantId) {
        return workflowDefinitionService.findByProcessDefineKeyAndLastVersion(
                procDefineKey,
                tenantId
        );
    }
}
